package com.hzgj.tool;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hzgj.bcl.util.lang.StrKit;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.xfer.FileSystemFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Created by Administrator on 2016/9/26.
 */
public class DeployTools {


    public static final String prefix = "/data/service/";
    private static final Config config = new Config();
    private static Logger logger = LoggerFactory.getLogger(DeployTools.class);
    private static final CountDownLatch latch;
    private static final Map<Node, List<String>> projects = Maps.newHashMap();
    private static Map<String, List<SSHClient>> startSequence = null;

    static {
        Properties properties = new Properties();
        try {
            properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties"));
            String source_dir = properties.getProperty("source_dir");
            String source_file = properties.getProperty("source_file");
            if (StrKit.notBlank(source_dir) && StrKit.notBlank(source_file)) {
                throw new RuntimeException("only one source_dir or source_file property exist");
            }
            config.sourceDir = source_dir;
            config.sourceFile = source_file;
            String _nodes = properties.getProperty("nodes");
            if (StrKit.notBlank(_nodes)) {
                List<Node> nodes = Lists.newArrayList();
                for (String _node : _nodes.split(",")) {
                    Node node = new Node();
                    node.host = _node;
                    node.key = Thread.currentThread().getContextClassLoader().getResource("id_rsa").getFile();
                    node.userName = properties.getProperty(_node);
                    String ps = properties.getProperty(_node + "_project");
                    if (StrKit.notBlank(ps)) {
                        projects.put(node, Lists.newArrayList(ps.split(",")));
                    }
                    nodes.add(node);
                }
                config.nodes = nodes;
            }
            config.targetPath = properties.getProperty("target_path");
            String _start_sequence = properties.getProperty("start.sequence");
            if (StrKit.notBlank(_start_sequence)) {
                startSequence = Maps.newLinkedHashMap();
                for (String project : _start_sequence.split(",")) {
                    startSequence.put(project, Lists.newArrayList());
                }
            }
        } catch (IOException e) {
            logger.error("{}", e);
        }
        latch = new CountDownLatch(config.nodes.size());
    }

    public static void main(String[] args) {
        DeployTools tool = new DeployTools();
        if (args.length == 0) {
            tool.deploy();
        } else {
            switch (args[0]) {
                case "deploy":
                    tool.deploy();
                    break;
                case "restart":
                    tool.restart();
                    break;
                case "delete":
                    tool.delete();
                    break;
                default:
                    throw new RuntimeException("illegal command");
            }
        }
    }

    void delete() {
        config.nodes.forEach(node -> {
            SSHClient sshClient = null;
            try {
                sshClient = connect(node);
                for (String project : projects.get(node)) {
                    try {
                        Exec.run(sshClient, getStop(project));
                        Exec.run(sshClient, getRmCommand(project));
                    } catch (Exception e) {
                        logger.error("{}", e);
                    }
                }
            } catch (Exception e) {
                logger.error("{}", e);
            } finally {
                if (sshClient != null && startSequence == null) {
                    try {
                        sshClient.disconnect();
                    } catch (IOException e) {
                    }
                }
            }
        });
    }

    void deploy() {
        config.nodes.forEach(node -> {
            deploy(node, latch);
        });

        try {
            logger.info("wait for node handle.");
            latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        beginStart();
        logger.info("over");
    }

    void restart() {
        config.nodes.forEach(node -> {
            SSHClient sshClient = null;
            try {
                sshClient = connect(node);
                for (String project : projects.get(node)) {
                    try {
                        Exec.run(sshClient, getStop(project));
                        if (startSequence != null) {
                            if (startSequence.get(project) != null) {
                                startSequence.get(project).add(sshClient);
                            }
                        } else {
                            Exec.run(sshClient, getStartCommand(project));
                        }
                    } catch (Exception e) {
                        logger.error("{}", e);
                    }
                }
            } catch (Exception e) {
                logger.error("{}", e);
            } finally {
                if (sshClient != null && startSequence == null) {
                    try {
                        sshClient.disconnect();
                    } catch (IOException e) {
                    }
                }
            }
        });
        beginStart();
    }

    void beginStart() {
        if (startSequence == null) {
            return;
        }
        startSequence.forEach((project, sshClients) -> {
            sshClients.forEach(sshClient -> {
                try {
                    TimeUnit.SECONDS.sleep(4);
                    Exec.run(sshClient, "source /etc/profile");
                    Exec.run(sshClient, getStartCommand(project));
                } catch (Exception e) {
                    logger.error("{}", e);
                }
            });
        });
        startSequence.values().forEach(sshClients -> sshClients.forEach(sshClient -> {
            try {
                sshClient.disconnect();
            } catch (IOException e) {
                logger.error("{}", e);
            }
        }));
    }

    void deploy(Node node, CountDownLatch latch) {
        SSHClient sshClient = null;
        try {
            sshClient = connect(node);
            SSHClient finalSshClient = sshClient;
            projects.get(node).forEach(project -> {
                try {
                    Exec.run(finalSshClient, getStop(project));
                    String target = config.targetPath.replace("#node#", project);
                    if (StrKit.notBlank(config.sourceFile)) {
                        upload(finalSshClient, config.sourceFile, target);
                    } else {
                        Exec.run(finalSshClient, getRmCommand(project));
                        File file = findTar(config.sourceDir, project);
                        if (!file.isDirectory()) {
                            logger.info("start to upload " + file.getName() + " to " + finalSshClient.getRemoteAddress());
                            upload(finalSshClient, file.getAbsolutePath(), target);
                            logger.info("finish upload " + file.getName());
                            Exec.run(finalSshClient, "tar -zxvf " + target + file.getName() + " -C " + target);
                            Exec.run(finalSshClient, "chmod +x " + target + project + "/app.sh ");
                        }
                    }
                    if (startSequence != null) {
                        if (startSequence.get(project) != null) {
                            startSequence.get(project).add(finalSshClient);
                        }
                    } else {
                        Exec.run(finalSshClient, getStartCommand(project));
                    }
                } catch (Exception e) {
                    logger.error("{}", e);
                }

            });
        } catch (Exception e) {
            logger.error("{}", e);
        } finally {
            latch.countDown();
            if (sshClient != null && startSequence == null) {
                try {
                    sshClient.disconnect();
                } catch (IOException e) {
                }
            }
        }
    }

    private File findTar(String dir, String project) {
        Collection<File> files = FileUtils.listFiles(new File(dir), new IOFileFilter() {
            @Override
            public boolean accept(File file) {
                return file.getName().equals(project + "-bin.tar.gz");
            }

            @Override
            public boolean accept(File dir, String name) {
                return false;
            }
        }, TrueFileFilter.INSTANCE);
        if (files != null && files.size() > 0) {
            return Lists.newArrayList(files).get(0);
        }
        throw new RuntimeException("find many target file with ext bin.tar.gz in " + dir);
    }


    private SSHClient connect(Node node) throws Exception {
        SSHClient ssh = new SSHClient();
        ssh.loadKnownHosts();
        ssh.connect(node.host);
        ssh.authPublickey(node.userName, node.key);
        return ssh;
    }

    private String getStop(String project) {
        return prefix + project + "/app.sh stop";
    }

    private String getRmCommand(String project) {
        return "rm -rf " + prefix + project + "*";
    }

    private String getStartCommand(String project) {
        return prefix + project + "/app.sh start";
    }

    void upload(SSHClient ssh, String src, String target) throws IOException {
        ssh.useCompression();
        ssh.newSCPFileTransfer().upload(new FileSystemFile(src), target);
    }


    static class Config {
        List<Node> nodes;
        private String sourceFile;
        private String sourceDir;
        private String targetPath;

    }

    static class Node {
        String host;
        String userName;
        String key;
    }
}
